package com.pai4j.pgc.service.resource;
import com.pai4j.common.constants.WebConstant;
import com.pai4j.common.util.DateUtils;
import com.pai4j.common.util.JsonUtil;
import com.pai4j.common.util.RedisUtil;
import com.pai4j.domain.community.vo.response.resource.ResourceCountResponseVO;
import com.pai4j.domain.community.vo.response.resource.ResourceDetailResponseVO;
import com.pai4j.domain.community.vo.response.resource.ResourceDownloadRecordVO;
import com.pai4j.domain.community.vo.response.resource.ResourceViewResponseVO;
import com.pai4j.domain.dto.resource.ResourceDownloadRecordDTO;
import com.pai4j.domain.vo.response.UserBaseResponseInfoVO;
import com.pai4j.pgc.dao.community.IResourceDAO;
import com.pai4j.pgc.dao.community.IResourceDownloadDAO;
import com.pai4j.pgc.entity.community.ResourceEntity;
import com.pai4j.pgc.pojo.convert.ResourceEntityConvert;
import com.pai4j.remote.user.UserServiceClient;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import com.pai4j.common.enums.RedisKeyEnum;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: 程序员七七
 * @Date: 15.12.21 1:00 上午
 * 资源缓存服务
 */
@Service
public class ResourceCacheService {

    @Autowired
    private UserServiceClient userServiceClient;
    @Autowired
    private IResourceDAO resourceDAO;
    @Autowired
    private IResourceDownloadDAO resourceDownloadDAO;
    @Autowired
    private RedisUtil redisUtil;

    /**
     * 文档默认封面图
     */
    private static final String DEFAULT_COVER = "http://coderutil.oss-cn-beijing.aliyuncs.com/bbs-image/file_4e367eaa7d0c463cb39699a8710e8c28.png";

    /***
     * 查询预览数据来自缓存
     * @param resourceId
     * @param currentUser
     * @return
     */
    public ResourceViewResponseVO getResourceViewDetailFromCache(String resourceId, String currentUser) {
        ResourceDetailResponseVO detail = getResourceDetailFromCache(resourceId, currentUser, true, true, true);
        if (detail == null) {
            return null;
        }
        // 详情对象转换为预览对象
        return ResourceEntityConvert.detail2View(detail);
    }

    /**
     * 查询资源详情
     * @param resourceId
     * @return
     */
    public ResourceDetailResponseVO getResourceDetailDetailFromCache(String resourceId) {
         return getResourceDetailFromCache(resourceId, null, true, true, true);
    }

    /***
     * 资源提交后缓存缓存任务
     * @param resourceEntity
     */
    public void refreshCacheBySubmitAfter(ResourceEntity resourceEntity) {
        this.refreshResourceDetailCacheByResourceEntity(resourceEntity);
    }

    /***
     * 根据资源ID缓存资源缓存
     * @param id
     * @return
     */
    public ResourceDetailResponseVO refreshResourceDetailCacheById(Long id) {
        ResourceEntity resourceEntity = resourceDAO.findById(id).orElse(null);
        return this.refreshResourceDetailCacheByResourceEntity(resourceEntity);
    }

    public Set<String> getCTCNewResourceFromCache(Long ccId, int pageNo, int pageSize) {
        String key = RedisKeyEnum.CTC_NEW_RESOURCE_LIST_CACHE.getKey(ccId.toString());
        if (!redisUtil.exists(key)) {
            initCTCNewResourceCache(ccId);
        }
        return redisUtil.zreverseRange(key, (pageNo - 1) * pageSize, pageNo * pageSize);
    }

    public void addCTCNewResourceCache(Long ccId, String resourceId) {
        String key = RedisKeyEnum.CTC_NEW_RESOURCE_LIST_CACHE.getKey(ccId.toString());
        if (!redisUtil.exists(key)) {
            initCTCNewResourceCache(ccId);
            return;
        }
        redisUtil.zadd(key, resourceId, DateUtils.getCurrentTimeMillis(), RedisKeyEnum.CTC_NEW_RESOURCE_LIST_CACHE.getExpireTime());
    }

    public void initCTCNewResourceCache(Long ccId) {
        String key = RedisKeyEnum.CTC_RESOURCE_REAL_TIME_LIST_CACHE.getKey(ccId.toString());
        List<ResourceEntity> resourceEntities = resourceDAO.findAllByCcIdAndReviewStatusInOrderByIdDesc(ccId, Arrays.asList(1, 2));
        if (CollectionUtils.isEmpty(resourceEntities)) {
            return;
        }
        Set<ZSetOperations.TypedTuple<String>> tuples = resourceEntities.stream().map(res -> {
            String value = String.valueOf(res.getResourceId());
            Double score = Double.valueOf(res.getCreateDate().getTime());
            return new DefaultTypedTuple<>(value, score);
        }).collect(Collectors.toSet());
        redisUtil.zadd(key, tuples, RedisKeyEnum.CTC_NEW_RESOURCE_LIST_CACHE.getExpireTime());
    }

    /***
     * 根据资源业务ID缓存资源缓存
     * @param resourceId
     * @return
     */
    public ResourceDetailResponseVO refreshResourceDetailCacheByResourceId(String resourceId) {
        ResourceEntity resourceEntity = resourceDAO.findAllByResourceId(resourceId);
        return this.refreshResourceDetailCacheByResourceEntity(resourceEntity);
    }

    /***
     * 根据资源实体缓存资源详情缓存
     * @param resourceEntity
     * @return
     */
    public ResourceDetailResponseVO refreshResourceDetailCacheByResourceEntity(ResourceEntity resourceEntity) {
        if (resourceEntity == null) {
            return null;
        }
        ResourceDetailResponseVO resourceDetailResponseVO = ResourceEntityConvert.convert(resourceEntity);
        String key = RedisKeyEnum.RESOURCE_DETAIL_CACHE.getKey(resourceEntity.getResourceId());
        redisUtil.set(key, JsonUtil.toJsonString(resourceDetailResponseVO), RedisKeyEnum.RESOURCE_DETAIL_CACHE.getExpireTime());
        return resourceDetailResponseVO;
    }

    /**
     * 查询资源详情缓存
     * 注意：这里返回的是ResourceDetailResponseVO对象，会返回资源的原始链接，调用的时候需要注意转换为业务需要的资源对象：VIEW、BASE
     * 需要主动屏蔽资源原始链接敏感数据
     *
     * @param resourceId
     * @param currentUser
     * @param queryAuthorInfo
     * @param queryCountInfo
     * @param queryBehaviorStatus
     * @return
     */
    private ResourceDetailResponseVO getResourceDetailFromCache(String resourceId, String currentUser,
                                                                boolean queryAuthorInfo,
                                                                boolean queryCountInfo,
                                                                boolean queryBehaviorStatus) {
        ResourceDetailResponseVO resourceDetailResponseVO;
        String key = RedisKeyEnum.RESOURCE_DETAIL_CACHE.getKey(resourceId);
        String val = redisUtil.get(key);
        if (StringUtils.isNotBlank(val)) {
            resourceDetailResponseVO = JsonUtil.fromJson(val, ResourceDetailResponseVO.class);
        } else {
            resourceDetailResponseVO = this.refreshResourceDetailCacheByResourceId(resourceId);
        }
        if (resourceDetailResponseVO == null) {
            return null;
        }
        if (queryAuthorInfo) {
            // 查询作者
            resourceDetailResponseVO.setAuthorInfo(userServiceClient.getUserBaseInfoByUserId(resourceDetailResponseVO.getAuthor()));
        }
        if (queryBehaviorStatus && StringUtils.isNotBlank(currentUser)) {
            resourceDetailResponseVO.setIsLike(false);
            resourceDetailResponseVO.setIsCollect(false);
        }
        if (queryCountInfo) {
            resourceDetailResponseVO.setResourceCount(new ResourceCountResponseVO());
        }
        return resourceDetailResponseVO;
    }

    /***
     * 批量查询资源数据
     * @param resourceIdList
     * @param needAuthor
     * @return
     */
    public Map<String, ResourceDetailResponseVO> batchGetResourceDetailFromCache(List<String> resourceIdList, boolean needAuthor, boolean queryCountInfo) {
        Map<String, ResourceDetailResponseVO> batchGetMap = new HashMap<>();
        if (CollectionUtils.isEmpty(resourceIdList)) {
            return batchGetMap;
        }
        List<String> cacheKeys = resourceIdList.stream().map(resId -> RedisKeyEnum.RESOURCE_DETAIL_CACHE.getKey(resId)).collect(Collectors.toList());
        List<String> cacheValues = redisUtil.mget(cacheKeys);
        List<String> authors = new ArrayList<>();
        for (int i = 0; i < resourceIdList.size(); i++) {
            String resourceId = resourceIdList.get(i);
            String value = cacheValues.get(i);
            ResourceDetailResponseVO resourceDetail = null;
            if (StringUtils.isNotBlank(value)) {
                resourceDetail = JsonUtil.fromJson(value, ResourceDetailResponseVO.class);
            } else {
                resourceDetail = this.refreshResourceDetailCacheByResourceId(resourceId);
            }
            if (resourceDetail != null) {
                if (StringUtils.isBlank(resourceDetail.getCoverImage())) {
                    // 给张默认封面
                    resourceDetail.setCoverImage(DEFAULT_COVER);
                }
                authors.add(resourceDetail.getAuthor());
                batchGetMap.put(resourceId, resourceDetail);
            }
        }
        /***
         * 需要作者，补充作者信息
         */
        if (needAuthor && CollectionUtils.isNotEmpty(authors)) {
            // 批量查询作者，从缓存查询
            Map<String, UserBaseResponseInfoVO> userInfoMap = userServiceClient.batchGetUserInfoFromCache(authors);
            batchGetMap.values().stream().forEach(detail -> detail.setAuthorInfo(userInfoMap.get(detail.getAuthor())));
        }
        if (queryCountInfo) {
            batchGetMap.values().stream().forEach(detail -> detail.setResourceCount(new ResourceCountResponseVO()));
        }
        return batchGetMap;
    }

    public Long increxResourceViewCountCache(String resourceId) {
        String key = RedisKeyEnum.RESOURCE_VIEW_COUNT_CACHE.getKey();
        return redisUtil.hincrex(key, resourceId);
    }

    public Long increxResourceDownloadCountCache(String resourceId) {
        String key = RedisKeyEnum.RESOURCE_DOWNLOAD_COUNT_CACHE.getKey();
        return redisUtil.hincrex(key, resourceId);
    }

    public void addDownloadUserSetCache(String resourceId, String userId) {
        String key = RedisKeyEnum.RESOURCE_DOWNLOAD_USER_SET_CACHE.getKey(resourceId);
        if (!redisUtil.exists(key)) {
            initDownloadUserSetCache(resourceId);
        }
        redisUtil.sadd(key, userId);
    }

    public void addDownloadRecordCache(Long ccid, String resourceId, String userId) {
        long time = new Date().getTime();
        ResourceDownloadRecordDTO resourceDownloadRecord = new ResourceDownloadRecordDTO(resourceId, userId, time);
        String key = RedisKeyEnum.RESOURCE_DOWNLOAD_LIST_CACHE.getKey();
        if (ccid != null) {
            String ctcKey = RedisKeyEnum.CTC_RESOURCE_DOWNLOAD_LIST_CACHE.getKey(ccid.toString());
            redisUtil.zadd(ctcKey, JsonUtil.toJsonString(resourceDownloadRecord), time);
        }
        redisUtil.zadd(key, JsonUtil.toJsonString(resourceDownloadRecord), time);
    }

    public List<ResourceDownloadRecordVO> getDownloadResourceList(int size) {
        String key = RedisKeyEnum.RESOURCE_DOWNLOAD_LIST_CACHE.getKey();
        return getDownloadResourceResources(key, size);
    }

    public List<ResourceDownloadRecordVO> getCTCDownloadResourceList(Long ccid, int size) {
        String key = RedisKeyEnum.CTC_RESOURCE_DOWNLOAD_LIST_CACHE.getKey(ccid.toString());
        return getDownloadResourceResources(key, size);
    }

    private List<ResourceDownloadRecordVO> getDownloadResourceResources(String redisKey, int size) {
        Set<String> resourceIds = redisUtil.zreverseRange(redisKey, 0, size);
        if (CollectionUtils.isEmpty(resourceIds)) {
            return Collections.emptyList();
        }
        List<ResourceDownloadRecordDTO> resourceDownloadRecords = resourceIds.stream().map(r -> {
            return JsonUtil.fromJson(r, ResourceDownloadRecordDTO.class);
        }).collect(Collectors.toList());
        List<String> downloadUserIdList = resourceDownloadRecords.stream().map(ResourceDownloadRecordDTO::getUserId)
                .collect(Collectors.toList());
        List<String> resourceIdStrList = resourceDownloadRecords.stream().map(ResourceDownloadRecordDTO::getResourceId)
                .collect(Collectors.toList());
        Map<String, UserBaseResponseInfoVO> userMap = userServiceClient.batchGetUserInfoFromCache(downloadUserIdList);
        List<ResourceDownloadRecordVO> list = new ArrayList<>();
        Map<String, ResourceDetailResponseVO> resourceDetailResponseVOMap =
                this.batchGetResourceDetailFromCache(resourceIdStrList, false, false);
        for (ResourceDownloadRecordDTO resourceDownloadRecord : resourceDownloadRecords) {
            ResourceDetailResponseVO resourceDetail = resourceDetailResponseVOMap.get(resourceDownloadRecord.getResourceId());
            ResourceDownloadRecordVO resourceDownloadRecordVO = new ResourceDownloadRecordVO();
            resourceDownloadRecordVO.setDownloadUser(userMap.get(resourceDownloadRecord.getUserId()));
            resourceDownloadRecordVO.setDownloadTime(resourceDownloadRecord.getDownloadTime());
            resourceDownloadRecordVO.setResource(resourceDetail);
            list.add(resourceDownloadRecordVO);
        }
        return list;
    }

    public Long getDownloadUserCountFromCache(String resourceId) {
        String key = RedisKeyEnum.RESOURCE_DOWNLOAD_USER_SET_CACHE.getKey(resourceId);
        if (!redisUtil.exists(key)) {
            initDownloadUserSetCache(resourceId);
        }
        return redisUtil.ssize(key);
    }

    private void initDownloadUserSetCache(String resourceId) {
        String key = RedisKeyEnum.RESOURCE_DOWNLOAD_USER_SET_CACHE.getKey(resourceId);
        List<String> userIdList = resourceDownloadDAO.findDistinctUserIdFromDownloadRecord(resourceId);
        if (CollectionUtils.isEmpty(userIdList)) {
            userIdList.add(WebConstant.CACHE_NONE);
        }
        redisUtil.sadd(key, new HashSet<>(userIdList));
    }

    /***
     * 批量查询文章浏览量
     * @param resourceIdList
     * @return
     */
    public Map<String, Long> batchGetResourceViewCountFromCache(List<String> resourceIdList) {
        Map<String, Long> resourceViewCountMap = new HashMap<>();
        if (CollectionUtils.isEmpty(resourceIdList)) {
            return resourceViewCountMap;
        }
        String key = RedisKeyEnum.RESOURCE_VIEW_COUNT_CACHE.getKey();
        List<String> viewCountList = redisUtil.hmget(key, resourceIdList);
        for (int i = 0; i < resourceIdList.size(); i++) {
            String resourceId = resourceIdList.get(i);
            String countValue = viewCountList.get(i);
            Long count = 0L;
            if (StringUtils.isNotBlank(countValue)) {
                count = Long.valueOf(countValue);
            } else {
                count = refreshResourceViewCount(resourceId);
            }
            resourceViewCountMap.put(resourceId, count);
        }
        return resourceViewCountMap;
    }

    /***
     * 批量查询文章下载量
     * @param resourceIdList
     * @return
     */
    public Map<String, Long> batchGetResourceDownloadCountFromCache(List<String> resourceIdList) {
        Map<String, Long> resourceViewCountMap = new HashMap<>();
        if (CollectionUtils.isEmpty(resourceIdList)) {
            return resourceViewCountMap;
        }
        String key = RedisKeyEnum.RESOURCE_DOWNLOAD_COUNT_CACHE.getKey();
        List<String> viewCountList = redisUtil.hmget(key, resourceIdList);
        for (int i = 0; i < resourceIdList.size(); i++) {
            String resourceId = resourceIdList.get(i);
            String countValue = viewCountList.get(i);
            Long count = 0L;
            if (StringUtils.isNotBlank(countValue)) {
                count = Long.valueOf(countValue);
            } else {
                count = refreshResourceDownloadCount(resourceId);
            }
            resourceViewCountMap.put(resourceId, count);
        }
        return resourceViewCountMap;
    }

    private Long refreshResourceDownloadCount(String resourceId) {
        Long count = resourceDownloadDAO.countAllByResourceId(resourceId);
        String key = RedisKeyEnum.RESOURCE_DOWNLOAD_COUNT_CACHE.getKey();
        redisUtil.hset(key, resourceId, String.valueOf(count), RedisKeyEnum.RESOURCE_VIEW_COUNT_CACHE.getExpireTime());
        return count;
    }

    private Long refreshResourceViewCount(String resourceId) {
        // TODO
        return 0L;
    }

    /***
     * 查询资源浏览量
     * @param resourceId
     * @return
     */
    public Long getResourceViewCountFromCache(String resourceId) {
        String key = RedisKeyEnum.RESOURCE_VIEW_COUNT_CACHE.getKey();
        String val = redisUtil.hget(key, resourceId);
        if (StringUtils.isNotBlank(val)) {
            return Long.valueOf(val);
        } else {
            redisUtil.hset(key, resourceId, "0", RedisKeyEnum.RESOURCE_VIEW_COUNT_CACHE.getExpireTime());
        }
        return 0L;
    }

    /***
     * 查询资源下载量
     * @param resourceId
     * @return
     */
    public Long getResourceDownloadCountFromCache(String resourceId) {
        String key = RedisKeyEnum.RESOURCE_DOWNLOAD_COUNT_CACHE.getKey();
        String val = redisUtil.hget(key, resourceId);
        if (StringUtils.isNotBlank(val)) {
            return Long.valueOf(val);
        } else {
            return refreshResourceDownloadCount(resourceId);
        }
    }
}
